package Encoder;

import com.cameras.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Title: JpegEncoder<br>
 * Description: Performs a jpeg compression of an image.<br>
 * 
 * @author Randr
 * 
 */
public class JpegEncoder extends Thread {
	int Total = 0;
	/**
	 * The stream where the Jpeg compression of the image is obtained.
	 */
	private ByteArrayOutputStream outStream = null;
	/**
	 * The info about the Jpeg image.
	 */
	private JpegInfo jpegObj = null;
	/**
	 * Huffman table derivation.
	 */
	private Huffman huf = null;
	/**
	 * DCT - A Java implementation of the Discreet Cosine Transform.
	 */
	private DCT dct = null;
	/**
	 * The height of the image.
	 */
	private int imageHeight = 0;
	/**
	 * The width of the image.
	 */
	private int imageWidth = 0;
	/**
	 * The quality of the image.<br>
	 * 0 to 100 and from bad image quality, high compression to good image
	 * quality low compression.
	 */
	private int quality = 50;

	private FileOutputStream fos;
	/**
	 * The jpeg natural order.
	 */
	private static int[] jpegNaturalOrder = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24,
			32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13,
			6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
			30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47,
			55, 62, 63, };

	private static FileInputStream audioIn;
	private static int audioIn_length;
	private static int length;
	private static int leavelength;

	MediaRecord MRecord;

	/**
	 * Constructs a new JpegEncoder.
	 * 
	 * @param image
	 *            The pixels of the image (R,G,B).
	 * @param imageQuality
	 *            The quality of the image.
	 * @param width
	 *            The width of the image.
	 * @param height
	 *            The hight of the image.
	 */
	public JpegEncoder(int[] image, int imageQuality, int width, int height,
			File audio, File output, MediaRecord MRecord)
			throws FileNotFoundException

	{

		if (image == null || width < 0 || height < 0) {
			return;
		}
		this.quality = imageQuality;
		fos = new FileOutputStream(output);
		audioIn = new FileInputStream(audio);
		System.out.println(audioIn);
		if (audioIn == null) {
			System.out.println("AudioIn error.");
			return;
		}
		audioIn_length = (int) audio.length();
		length = audioIn_length / 4;
		leavelength = audioIn_length % 4;
		System.out.println("byte counts = " + audioIn_length);// 印出的是byte個數

		/*
		 * Getting picture information It takes the Width, Height and RGB scans
		 * of the image.
		 */
		this.jpegObj = new JpegInfo(image, width, height);

		this.imageHeight = height;
		this.imageWidth = width;
		this.outStream = new ByteArrayOutputStream();
		this.dct = new DCT(quality);
		this.huf = new Huffman(imageWidth, imageHeight);
		this.MRecord = MRecord;
	}

	/**
	 * Gets the byte array representation of the Jpeg compressed image.
	 * 
	 * @return The byte array representing the Jpeg compressed inmage.
	 */
	public byte[] getByteArray() {
		if (this.outStream == null) {
			return null;
		}
		return this.outStream.toByteArray();
	}

	/**
	 * Sets the quality of the Jpeg image.
	 * 
	 * @param quality
	 *            The quality of the Jpeg image.
	 * @see #getQuality
	 */
	public void setQuality(int quality) {
		dct = new DCT(quality);
	}

	/**
	 * Gets the quality of the Jpeg image.
	 * 
	 * @return The quality of the Jpeg image.
	 * @see #setQuality
	 */
	public int getQuality() {
		return quality;
	}

	/**
	 * Proceeds the compression of the Jpeg image.
	 * 
	 * @throws ImageRuntimeException
	 *             if an exception occurs during the compression.
	 */
	public void compress() throws ImageRuntimeException, IOException {
		if (this.outStream == null) {
			return;
		}
		writeHeaders(outStream);
		writeCompressedData(outStream);
		writeEOI(outStream);
		try {
			outStream.flush();
		} catch (IOException e) {
			throw new ImageRuntimeException(
					ImageRuntimeException.failedToWriteMsg,
					ImageRuntimeException.failedToWriteCode);
		}
	}

	/**
	 * Writes the compressed data into the output stream.
	 * 
	 * @param outStream
	 *            The output stream where to write the compressed data.
	 */
	private void writeCompressedData(ByteArrayOutputStream outStream)
			throws IOException {
		int i = 0;
		int j = 0;
		int r = 0;
		int c = 0;
		int a = 0;
		int b = 0;
		int comp = 0;
		int xpos = 0;
		int ypos = 0;
		int xblockoffset = 0;
		int yblockoffset = 0;
		float[][] inputArray = null;
		float[][] dctArray1 = new float[8][8];
		double[][] dctArray2 = new double[8][8];
		int[] dctArray3 = new int[8 * 8];
		Hide hi = new Hide();
		boolean first = true;
		byte[] data = new byte[4];
		int g = 0;
		byte[] data1 = new byte[1];

		/*
		 * This method controls the compression of the image. Starting at the
		 * upper left of the image, it compresses 8x8 blocks of data until the
		 * entire image has been compressed.
		 */

		int[] lastDCvalue = new int[jpegObj.getNumberOfComponents()];
		int width = 0;
		int height = 0;
		int minBlockWidth = 0;
		int minBlockHeight = 0;
		// This initial setting of MinBlockWidth and MinBlockHeight is done to
		// ensure they start with values larger than will actually be the case.
		if (imageWidth % 8 != 0) {
			minBlockWidth = (int) (Math.floor(imageWidth / 8.0) + 1) * 8;
		} else {
			minBlockWidth = imageWidth;
		}
		if (imageHeight % 8 != 0) {
			minBlockHeight = (int) (Math.floor(imageHeight / 8.0) + 1) * 8;
		} else {
			minBlockHeight = imageHeight;
		}
		final int noComp = jpegObj.getNumberOfComponents();
		for (comp = 0; comp < noComp; comp++) {
			minBlockWidth = Math
					.min(minBlockWidth, jpegObj.getBlockWidth(comp));
			minBlockHeight = Math.min(minBlockHeight,
					jpegObj.getBlockHeight(comp));
		}
		xpos = 0;
		System.out.println("minBlockWidth " + minBlockWidth
				+ " minBlockHeight " + minBlockHeight);
		for (r = 0; r < minBlockHeight; r++) {
			for (c = 0; c < minBlockWidth; c++) {
				xpos = c * 8;
				ypos = r * 8;
				for (comp = 0; comp < noComp; comp++) {
					width = jpegObj.getBlockWidth(comp);
					height = jpegObj.getBlockHeight(comp);
					inputArray = (float[][]) jpegObj.getComponent(comp);

					for (i = 0; i < JpegInfo.vsampFactor[comp]; i++) {
						for (j = 0; j < JpegInfo.hsampFactor[comp]; j++) {
							xblockoffset = j * 8;
							yblockoffset = i * 8;
							for (a = 0; a < 8; a++) {
								for (b = 0; b < 8; b++) {
									dctArray1[a][b] = inputArray[ypos
											+ yblockoffset + a][xpos
											+ xblockoffset + b];
								}
							}
							dctArray2 = dct.forwardDCT(dctArray1);
							dctArray3 = dct.quantizeBlock(dctArray2,
									JpegInfo.qtableNumber[comp]);
							// 是不是在這裡去做加入的動作?yes
							if (first) {
								hi.Writelength(dctArray3, audioIn_length);// audioIn_length/4之所以要把byte長度除4是因為一個block(8*8)，可以放4個bytes.
								// System.out.println("for test:"+"\naudioIn_length = "+audioIn_length+"\n,audioIn_length/4 = "
								// + audioIn_length/4
								// +"\n(int)java.lang.Math.ceil(audioIn_length/4) = "
								// +
								// (int)java.lang.Math.ceil(audioIn_length/4));
								System.out.println();
								first = false;
							} else {
								if (length > 0) {
									audioIn.read(data);
									for (int z = 0; z < data.length; z++) {
										System.out.printf("%x ", data[z]);
									}
									System.out.println();
									// hi.print(dctArray3);
									hi.WritelData(dctArray3, data);// int
																	// []p,byte
																	// []data;//p[64]
																	// data[4]
									// hi.print(dctArray3);
									// System.out.println();
									length--;
								} else if (length == 0 && leavelength > 0) {
									for (int t = leavelength; t > 0; t--) {
										System.out.printf("末%d( ", t);
										audioIn.read(data1);// 一個一個byte讀取
										System.out.printf("%x )", data1[0]);
										data[leavelength - t] = data1[0];// 再放到暫存
									}
									for (int y = 3; y >= leavelength; y--) {// 做補0的動作
										data[y] = 0x00;
									}
									System.out.printf("\n末4byte:最後%d個補0\n",
											4 - leavelength);
									for (int z = 0; z < data.length; z++) {
										System.out.printf("%x ", data[z]);
									}
									System.out.println();
									hi.WritelData(dctArray3, data);// int
																	// []p,byte
																	// []data;//p[64]
																	// data[4]
									length--;// 停止條件
									// //////////////////////////////////////舊資料分隔線////////////////////
									/*
									 * //這邊考慮的是先讀再補0的蠢方法，不用這個。
									 * audioIn.read(data);
									 * System.out.printf("\n剛讀進來的末4byte:");
									 * for(int z=0;z<data.length;z++){
									 * System.out.printf("%x ",data[z]); }
									 * for(int
									 * y=3;y>=audioIn_length%4;y--){//做補0的動作
									 * data[y]=0x00; }
									 * System.out.printf("\n補0後的末4byte:");
									 * for(int z=0;z<data.length;z++){
									 * System.out.printf("%x ",data[z]); }
									 * System.out.println();
									 */
								}
							}

							/*
							 * System.out.print("Total "+(Total++)+" " );
							 * for(int o=0;o<64;o++){
							 * System.out.print(dctArray3[o]+" "); }
							 */
							huf.huffmanBlockEncoder(outStream, dctArray3,
									lastDCvalue[comp],
									JpegInfo.dCtableNumber[comp],
									JpegInfo.aCtableNumber[comp]);
							lastDCvalue[comp] = dctArray3[0];
						}
					}
				}
			}
		}
		huf.flushBuffer(outStream);
		this.audioIn.close();
	}

	/**
	 * Writes the EOI marker to the output stream.
	 * 
	 * @param out
	 *            The output stream where to write the compressed data.
	 */
	private void writeEOI(ByteArrayOutputStream out) {
		byte[] mEOI = { (byte) 0xFF, (byte) 0xD9 };
		writeMarker(mEOI, out);
	}

	/**
	 * Writes the headers of the file into the output stream.
	 * 
	 * @param out
	 *            The output stream where to write the compressed data.
	 */
	private void writeHeaders(ByteArrayOutputStream out) {
		int i = 0;
		int j = 0;
		int index = 0;
		int offset = 0;
		int length = 0;
		int[] tempArray = null;

		// the SOI marker
		byte[] mSOI = { (byte) 0xFF, (byte) 0xD8 };
		writeMarker(mSOI, out);

		// The order of the following headers is quiet inconsequential.
		// the JFIF header
		byte[] aJFIF = new byte[18];
		aJFIF[0] = (byte) 0xff;
		aJFIF[1] = (byte) 0xe0;
		aJFIF[2] = (byte) 0x00;
		aJFIF[3] = (byte) 0x10;
		aJFIF[4] = (byte) 0x4a;
		aJFIF[5] = (byte) 0x46;
		aJFIF[6] = (byte) 0x49;
		aJFIF[7] = (byte) 0x46;
		aJFIF[8] = (byte) 0x00;
		aJFIF[9] = (byte) 0x01;
		aJFIF[10] = (byte) 0x00;
		aJFIF[11] = (byte) 0x00;
		aJFIF[12] = (byte) 0x00;
		aJFIF[13] = (byte) 0x01;
		aJFIF[14] = (byte) 0x00;
		aJFIF[15] = (byte) 0x01;
		aJFIF[16] = (byte) 0x00;
		aJFIF[17] = (byte) 0x00;
		writeArray(aJFIF, out);

		// Comment Header
		String comment = new String();
		comment = jpegObj.getComment();
		length = comment.length();
		byte[] mCOM = new byte[length + 4];
		mCOM[0] = (byte) 0xFF;
		mCOM[1] = (byte) 0xFE;
		mCOM[2] = (byte) ((length >> 8) & 0xFF);
		mCOM[3] = (byte) (length & 0xFF);
		java.lang.System.arraycopy(jpegObj.getComment().getBytes(), 0, mCOM, 4,
				jpegObj.getComment().length());
		writeArray(mCOM, out);

		// The DQT header
		// 0 is the luminance index and 1 is the chrominance index
		byte[] hDQT = new byte[134];
		hDQT[0] = (byte) 0xFF;
		hDQT[1] = (byte) 0xDB;
		hDQT[2] = (byte) 0x00;
		hDQT[3] = (byte) 0x84;
		offset = 4;
		for (i = 0; i < 2; i++) {
			hDQT[offset++] = (byte) ((0 << 4) + i);
			tempArray = (int[]) dct.getQuantum(i);
			for (j = 0; j < 64; j++) {
				hDQT[offset++] = (byte) tempArray[jpegNaturalOrder[j]];
			}
		}
		writeArray(hDQT, out);

		// Start of Frame Header
		byte[] mSOF = new byte[19];
		mSOF[0] = (byte) 0xFF;
		mSOF[1] = (byte) 0xC0;
		mSOF[2] = (byte) 0x00;
		mSOF[3] = (byte) 17;
		mSOF[4] = (byte) JpegInfo.precision;
		mSOF[5] = (byte) ((jpegObj.getImageHeight() >> 8) & 0xFF);
		mSOF[6] = (byte) ((jpegObj.getImageHeight()) & 0xFF);
		mSOF[7] = (byte) ((jpegObj.getImageWidth() >> 8) & 0xFF);
		mSOF[8] = (byte) ((jpegObj.getImageWidth()) & 0xFF);
		mSOF[9] = (byte) jpegObj.getNumberOfComponents();
		index = 10;
		for (i = 0; i < mSOF[9]; i++) {
			mSOF[index++] = (byte) JpegInfo.compID[i];
			mSOF[index++] = (byte) ((JpegInfo.hsampFactor[i] << 4) + JpegInfo.vsampFactor[i]);
			mSOF[index++] = (byte) JpegInfo.qtableNumber[i];
		}
		writeArray(mSOF, out);

		// The DHT Header
		byte[] hDHT1 = null;
		byte[] hDHT2 = null;
		byte[] hDHT3 = null;
		byte[] hDHT4 = null;
		int bytes = 0;
		int temp = 0;
		int oldindex = 0;
		int intermediateindex = 0;
		length = 2;
		index = 4;
		oldindex = 4;
		hDHT1 = new byte[17];
		hDHT4 = new byte[4];
		hDHT4[0] = (byte) 0xFF;
		hDHT4[1] = (byte) 0xC4;
		for (i = 0; i < 4; i++) {
			bytes = 0;
			hDHT1[index++ - oldindex] = (byte) ((int[]) huf.getBits()
					.elementAt(i))[0];
			for (j = 1; j < 17; j++) {
				temp = ((int[]) huf.getBits().elementAt(i))[j];
				hDHT1[index++ - oldindex] = (byte) temp;
				bytes += temp;
			}
			intermediateindex = index;
			hDHT2 = new byte[bytes];
			for (j = 0; j < bytes; j++) {
				hDHT2[index++ - intermediateindex] = (byte) ((int[]) huf
						.getVal().elementAt(i))[j];
			}
			hDHT3 = new byte[index];
			java.lang.System.arraycopy(hDHT4, 0, hDHT3, 0, oldindex);
			java.lang.System.arraycopy(hDHT1, 0, hDHT3, oldindex, 17);
			java.lang.System.arraycopy(hDHT2, 0, hDHT3, oldindex + 17, bytes);
			hDHT4 = hDHT3;
			oldindex = index;
		}
		hDHT4[2] = (byte) (((index - 2) >> 8) & 0xFF);
		hDHT4[3] = (byte) ((index - 2) & 0xFF);
		writeArray(hDHT4, out);

		// Start of Scan Header
		byte[] mSOS = new byte[14];
		mSOS[0] = (byte) 0xFF;
		mSOS[1] = (byte) 0xDA;
		mSOS[2] = (byte) 0x00;
		mSOS[3] = (byte) 12;
		mSOS[4] = (byte) jpegObj.getNumberOfComponents();
		index = 5;
		for (i = 0; i < mSOS[4]; i++) {
			mSOS[index++] = (byte) JpegInfo.compID[i];
			mSOS[index++] = (byte) ((JpegInfo.dCtableNumber[i] << 4) + JpegInfo.aCtableNumber[i]);
		}
		mSOS[index++] = (byte) jpegObj.getSs();
		mSOS[index++] = (byte) jpegObj.getSe();
		mSOS[index++] = (byte) ((jpegObj.getAh() << 4) + jpegObj.getAl());
		writeArray(mSOS, out);

	}

	/**
	 * Writes a marker to the output stream.
	 * 
	 * @param data
	 *            The marker contained data.
	 * @param out
	 *            The output stream.
	 */
	private void writeMarker(byte[] data, ByteArrayOutputStream out) {
		out.write(data, 0, 2);
	}

	/**
	 * Writes an byte array to the output stream.
	 * 
	 * @param data
	 *            The array.
	 * @param out
	 *            The output stream.
	 */
	private void writeArray(byte[] data, ByteArrayOutputStream out) {
		int length = 0;
		length = (((int) (data[2] & 0xFF)) << 8) + (int) (data[3] & 0xFF) + 2;
		out.write(data, 0, length);
	}

	@Override
	public void run() { // override Thread's run()
		try {
			MRecord.join();
			// override Thread's run()
			this.compress();
			fos.write(getByteArray());
			fos.flush();
			fos.close();
			//System.out.println("End");

		} catch (ImageRuntimeException ex) {
			Logger.getLogger(JpegEncoder.class.getName()).log(Level.SEVERE,
					null, ex);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException ex) {
			Logger.getLogger(JpegEncoder.class.getName()).log(Level.SEVERE,
					null, ex);
		}
	}
}
